/*
 * Copyright (c) 2024 AlgoIdeas <yu19881234@163.com>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h> 

#include "ohos_types.h"
#include "iot_errno.h"
#include "iot_gpio.h"
#include "iot_i2c.h"
#include "iot_gpio_ex.h"

#include "ohos_init.h"
#include "cmsis_os2.h"

#define NTP_CLINET_ON   1
#ifdef NTP_CLINET_ON
#include "NTPClient.h"
#endif

#include <cJSON.h>

#include "ble.h"
#include "sle.h"

#include "mqtt.h"
#include "peripheral.h"
#include "wifi_connect.h"

#define BLE_CONNECT_ON      1
#define SLE_CONNECT_ON      1
#define WIFI_CONNECT_ON     1

#define SOFT_VERSION    "Ver 0.0.1("__DATE__" "__TIME__")"

#define NTP_SERVER_IP       "119.28.183.184" // 腾讯云
#define NTP_SERVER_PORT     123

#define IRRIGATE_MAX_THRESHOLD  100
#define CHNUK_SIZE  250

#ifdef WIFI_CONNECT_ON
#define WIFI_SSID   "C403"
#define WIFI_PASSWORD "c403c403"

#define MAX_CONNECT_RETRY_TIMES 16
#define CONNECT_ATTEMPT_INTERVAL_MS 300
#endif

#ifdef NTP_CLINET_ON
static NTPClient ntpClient;
#endif

static struct IrrigateTypeName irrigateTypeNames[] = {
    { IRRIGATE_DATA, "Data" },
    { IRRIGATE_IMMEDIATELY, "Irrigate" },
    { IRRIGATE_INTERVAL, "IrrigateInterval" },
    { IRRIGATE_THRESHOLD, "IrrigateThreshold" },
    { IRRIGATE_STOP, "IrrigateStop" }
};

static int32_t irrigateInterval = 0;
static int32_t irrigateThreshold = 0;
static int32_t irrigateCurInterval = 0;
static int32_t irrigateCurThreshold = 0;


/*
 * ****************************************************
 * NTP Application
 * ****************************************************
 */
#if defined (WIFI_CONNECT_ON) && defined (NTP_CLINET_ON)
static void NtpTask(int *arg)
{
    osal_printk("Starting NtpTask ...\r\n");
    (void)arg;
    int32_t ret = NTP_OK;
    bool ntpConnected = false;
    struct tm *localTime;
    int8_t timeStr[64];
    int64_t ntpTime;

    osal_printk("WiFiConnect ...\r\n");
    WiFiConnect(WIFI_SSID, WIFI_PASSWORD);

    for (;;) {
        if (WiFiGetState() == WIFI_CONNECTED) {
            osal_printk("Wifi Connected.\r\n");
            break;
        }
        osDelay(1000);
    }

    memset(&ntpClient, 0x0, sizeof(NTPClient));
    sprintf(ntpClient.serverIP, "%s", NTP_SERVER_IP);
    ntpClient.serverPort = NTP_SERVER_PORT;

    osal_printk("NTP Time Sync.\r\n");

    for (;;)
    {
        if (!ntpConnected) {
            ret = NTPClientConnect(&ntpClient);
            if (ret == NTP_ERR) {
                osDelay(100);
                continue;
            }
            osal_printk("NTPClient Connected\r\n");
            ntpConnected = true;
        }

        ntpTime = NTPClientSync(&ntpClient);
        if (ntpTime == NTP_ERR) {
            osDelay(10000);
            continue;
        }

        localTime = localtime((time_t *)&ntpTime);
        memset(timeStr, 0x0, sizeof(timeStr));
        snprintf(timeStr, 64, "%d/%d/%d %02d:%02d:%02d",
             1900 + localTime->tm_year,
             1 + localTime->tm_mon,
             localTime->tm_mday,
             localTime->tm_hour,
             localTime->tm_min,
             localTime->tm_sec);

        osal_printk("Ntp time : %s\r\n", timeStr);
        osDelay(10000);
    }
}

static void Ntp(void)
{
    osThreadAttr_t attr;
    attr.name = "NtpTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 20480;
    attr.priority = osPriorityNormal;

    if (osThreadNew((osThreadFunc_t)NtpTask, NULL, &attr) == NULL) {
        osal_printk("Falied to create NtpTask!\n");
    }
}
SYS_RUN(Ntp);
#endif

static void sendData(const char *string, int length, int chunk_size) {
    for (int i = 0; i < length; i += chunk_size) {
        int current_chunk_size = (i + chunk_size > length) ? (length - i) : chunk_size;
#ifdef BLE_CONNECT_ON
        ble_send(string + i, current_chunk_size);
#endif

#ifdef SLE_CONNECT_ON
        sle_send(string + i, current_chunk_size);
#endif
    }
}

static void responseMessage(IRRIGATE_TYPE_T type)
{
    char payload[256];
    int ret;
    struct timeval tv;
    int64_t curTime;
    uint32_t i = 0;

    gettimeofday(&tv, NULL);
    curTime = tv.tv_sec;

    for (i = 0; i < ARRAY_SIZE(irrigateTypeNames); i++) {
        if (irrigateTypeNames[i].type == type) {
            break;
        }
    }

    memset(payload, 0x0, sizeof(payload));
    snprintf(payload, 256, "{\"Time\":%lld,\"AreaCode\":\"ES001\",\"DeviceId\":\"%s\",\"Data\":{\"Type\": \"3\", \"Response\": \"Success\"}}", 
        curTime, MQTT_CLIENT_ID);

    sendData(payload, strlen(payload), CHNUK_SIZE);
}

static void processMessage(char *message)
{
    IRRIGATE_TYPE_T type = IRRIGATE_DATA;
    cJSON *objRoot;
    cJSON *objDevice;
    cJSON *objData;
    cJSON *objType;
    cJSON *objPara;

    objRoot = cJSON_Parse(message);
    if (objRoot == NULL) {
        goto EXIT_JSONPARSE;
    }

    objDevice = cJSON_GetObjectItem(objRoot, "DeviceId");
    if (objDevice == NULL) {
        goto EXIT_OBJTYPE;
    }

    if (0 != strncmp(cJSON_GetStringValue(objDevice), MQTT_CLIENT_ID, strlen(MQTT_CLIENT_ID))) {
        osal_printk("Ignore Device\n");
        goto EXIT_OBJTYPE;
    }

    objData = cJSON_GetObjectItem(objRoot, "Data");
    if (objData == NULL) {
        goto EXIT_OBJTYPE;
    }

    objType = cJSON_GetObjectItem(objData, "Type");
    if (objType == NULL) {
        goto EXIT_OBJTYPE;
    }

    for (uint32_t i = 0; i < ARRAY_SIZE(irrigateTypeNames); i++) {
        if (!strcmp(cJSON_GetStringValue(objType), (const char*)irrigateTypeNames[i].name)) {
            type = irrigateTypeNames[i].type;
            break;
        }
    }

    switch (type) {
        case IRRIGATE_IMMEDIATELY:
        {
            osal_printk("irrigate immediately!\r\n");
            irrigateInterval = 0;
            irrigateThreshold = 0;
            WaterPumpInOnOff(true);
            responseMessage(IRRIGATE_IMMEDIATELY);
            break;
        }
        case IRRIGATE_INTERVAL:
        {
            osal_printk("irrigate interval\r\n");
            objPara = cJSON_GetObjectItem(objData, "Interval");
            if (objPara == NULL) {
                goto EXIT_OBJPARA;
            }
            irrigateThreshold = 0;
            irrigateCurThreshold = 0;
            irrigateCurInterval = 0;
            irrigateInterval = (int32_t)cJSON_GetNumberValue(objPara);
            osal_printk("interval %d\r\n", irrigateInterval);
            responseMessage(IRRIGATE_INTERVAL);
            break;
        }
        case IRRIGATE_THRESHOLD:
        {
            osal_printk("irrigate threshold!\r\n");
            objPara = cJSON_GetObjectItem(objData, "Interval");
            if (objPara == NULL) {
                goto EXIT_OBJPARA;
            }
            irrigateInterval = 0;
            irrigateCurInterval = 0;
            irrigateCurThreshold = 0;
            irrigateThreshold = (int32_t)cJSON_GetNumberValue(objPara);
            osal_printk("threshold %d\r\n", irrigateThreshold);
            irrigateThreshold = (irrigateThreshold > IRRIGATE_MAX_THRESHOLD) ? IRRIGATE_MAX_THRESHOLD : irrigateThreshold;
            WaterPumpInOnOff(true);
            responseMessage(IRRIGATE_THRESHOLD);
            break;
        }
        case IRRIGATE_STOP:
        {
            osal_printk("irrigate stop!\r\n");
            irrigateInterval = 0;
            irrigateThreshold = 0;
            WaterPumpInOnOff(false);
            responseMessage(IRRIGATE_STOP);
            break;
        }
        default:
            break;
    }

EXIT_OBJPARA:
EXIT_OBJTYPE:
    cJSON_Delete(objRoot);
EXIT_JSONPARSE:
    return;
}

static void onSleReadRequest(uint8_t server_id, uint16_t conn_id, ssaps_req_read_cb_t *read_cb_para,
    errcode_t status)
{
    osal_printk("[SLE] recv read server_id:%d\r\n", server_id);
}

static void onSleWriteRequest(uint8_t server_id, uint16_t conn_id, uint8_t *buffer, uint16_t length)
{
    osal_printk("[SLE] recv write server_id:%d, buffer:%s\r\n", server_id, buffer);
    char payload[256];
    uint16_t len = (length >= 256) ? 256 : length;

    if (length > 0) {
        memset(payload, 0x0, sizeof(payload));
        snprintf(payload, len, "%s", buffer);
        processMessage(payload);
    }
}

static void onBleReceiveRead(uint8_t server_id, uint16_t conn_id, gatts_req_read_cb_t *read_cb_para,
    errcode_t status)
{
    osal_printk("[BLE] recv read server_id:%d\r\n", server_id);
}

static void onBleReceiveWrite(uint8_t server_id, uint16_t conn_id, uint8_t *buffer, uint16_t length)
{
    osal_printk("[BLE] recv write server_id:%d, buffer:%s\r\n", server_id, buffer);
    char payload[256];
    uint16_t len = (length >= 256) ? 256 : length;

    if (length > 0) {
        memset(payload, 0x0, sizeof(payload));
        snprintf(payload, len, "%s", buffer);
        processMessage(payload);
    }
}

static void MainTask(void)
{
    osal_printk("Starting MainTask ...\n");
    int32_t tryCount = 0;
    int32_t isConnected = 0;
    int32_t waitSeconds = 10;
    int32_t ret = 0;
    int32_t count = 0;
    char payload[256];
    struct timeval tv;
    struct tm *localTime;
    char timeStr[64];
    int64_t curTime;
    float temp = 0.0;
    float humi = 0.0;
    float lux = 0.0;
    bool onoff = false;

    osal_printk("Smart Agriculture IOT : %s\r\n", SOFT_VERSION);

    osal_printk("PeripheralInit ...\r\n");
    PeripheralInit();

    osal_printk("BLE Register ...\r\n");
    ble_register_receive_callback(onBleReceiveRead, onBleReceiveWrite);

    osal_printk("SLE Register ...\r\n");
    sle_register_request_callback(onSleReadRequest, onSleWriteRequest);

#ifdef WIFI_CONNECT_ON
    LedOnOff(LED_RED, true);

    for (;;) {
        if (WiFiGetState() == WIFI_CONNECTED) {
            LedOnOff(LED_RED, false);
            osal_printk("Wifi Connect success.\r\n");
            osDelay(1000);
            break;
        }
        osDelay(1000);
    }
#endif

    osal_printk("Main Loop.\r\n");

    for (;;)
    {
        gettimeofday(&tv, NULL);
        localTime = localtime((time_t *)&tv.tv_sec);

        memset(timeStr, 0x0, sizeof(timeStr));
        snprintf(timeStr, 64, "%d/%d/%d %02d:%02d:%02d",
             1900 + localTime->tm_year,
             1 + localTime->tm_mon,
             localTime->tm_mday,
             localTime->tm_hour,
             localTime->tm_min,
             localTime->tm_sec);

        osal_printk("Current time : %s\r\n", timeStr);

        if (irrigateInterval > 0) {
            irrigateCurInterval ++;
            if (0 == (irrigateCurInterval % irrigateInterval)) {
                count ++;
            }

            if (0 == (count % 2)) {
                onoff = !onoff;
                WaterPumpInOnOff(onoff);
            }
        }

        if (irrigateThreshold > 0) {
            irrigateCurThreshold ++;
            if (irrigateCurThreshold >= irrigateThreshold) {
                irrigateThreshold = 0;
                WaterPumpInOnOff(false);
            }
        }

        /* Read Sensor */
        SensorRead(&temp, &humi, &lux);

        gettimeofday(&tv, NULL);
        curTime = tv.tv_sec;

        memset(payload, 0x0, sizeof(payload));
        snprintf(payload, 256, "{\"Time\":%lld,\"AreaCode\":\"ES001\",\"DeviceId\":\"%s\",\"Data\": {\"Type\": \"2\", \"Temperature\": %.2f, \"Humidity\": %.2f, \"Illumination\": %.2f}}", 
                curTime, MQTT_CLIENT_ID, temp, humi, lux);
        osal_printk("payload : %s\r\n", payload);
        sendData(payload, strlen(payload), CHNUK_SIZE);

        osDelay(1000);
    }
}

/*
 * ****************************************************
 * Main
 * ****************************************************
 */
static void Main(void)
{
    osal_printk("Main.\r\n");
    osThreadAttr_t attr;
    attr.name = "MainTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 40960;
    attr.priority = osPriorityNormal;

    if (osThreadNew((osThreadFunc_t)MainTask, NULL, &attr) == NULL) {
        osal_printk("Falied to create MainTask!\n");
    }
}
APP_FEATURE_INIT(Main);
